package org.zjvis.datascience.common.graph.algo;

import org.zjvis.datascience.common.graph.util.GraphUtil;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description ClusteringCoefficient
 * @date 2021-12-29
 */
public class ClusteringCoefficient {
    private Integer N;
    private Integer totalTriangles;
    private Double avgClusteringCoeff;

    private Map<Set<Object>, EdgeWrapper> edgeWrappers;
    private Map<Object, List<Object>> neighborMap;

    private Map<Object, Double> clusteringCoefficientMap;
    private Map<Object, Integer> trianglesMap;

    private ConcurrentMap<Object, AtomicInteger> concurrentTrianglesMap;


    public ClusteringCoefficient(Map<Object, List<Object>> neighborMap) {
        this.neighborMap = neighborMap;
        this.init();
    }

    public void init() {
        N = neighborMap.size();
        avgClusteringCoeff = 0.0D;
        totalTriangles = 0;
        clusteringCoefficientMap = new HashMap<>();
        trianglesMap = new HashMap<>();
        concurrentTrianglesMap = new ConcurrentHashMap<>();


        edgeWrappers = new HashMap<>();
//        int index = 0;
        Iterator<Object> it = neighborMap.keySet().iterator();
        while (it.hasNext()) {
            Object id = it.next();
            Iterator<Object> itNbr = neighborMap.get(id).iterator();
            while (itNbr.hasNext()) {
                Object nbr = itNbr.next();
                edgeWrappers.putIfAbsent(new HashSet<Object>() {{
                    add(id);
                    add(nbr);
                }}, new EdgeWrapper(id, nbr));
            }
//            id2index.put(id, index);
//            index2Id.put(index, id);
//            index++;
            clusteringCoefficientMap.put(id, 0.0D);
            trianglesMap.put(id, 0);
            concurrentTrianglesMap.put(id, new AtomicInteger(0));
        }
    }

    public void execute() {
        for (EdgeWrapper edge: edgeWrappers.values()) {
            processEdge(edge);
        }
        computeResultValue();
    }

    public void computeResultValue() {
        Double currentClusteringCoefficient = 0.0D;
        for (Map.Entry<Object, AtomicInteger> entry: concurrentTrianglesMap.entrySet()) {
            Object id = entry.getKey();
            Integer triangles = entry.getValue().get() / 2;
            trianglesMap.put(id, triangles);
            totalTriangles += triangles;
            Integer nodeDegree = neighborMap.get(id).size();
            if (nodeDegree > 1) {
                Double cc = triangles * 1.0D;
                cc /= nodeDegree * (nodeDegree - 1);
                cc *= 2.0D;
                currentClusteringCoefficient += cc;
                clusteringCoefficientMap.put(id, GraphUtil.round2DecimalDouble(cc));
            }
        }
        avgClusteringCoeff = N == 0 ? 0.0D: GraphUtil.round2DecimalDouble(currentClusteringCoefficient / N);
        totalTriangles = totalTriangles / 3;
    }

    public void processEdge(EdgeWrapper edge) {
        Object src = edge.source;
        Object tar = edge.target;
        List<Object> srcNbrs = neighborMap.get(src);
        List<Object> tarNbrs = neighborMap.get(tar);
        List<Object> smallNbrs;
        List<Object> largeNbrs;
        if (srcNbrs.size() < tarNbrs.size()) {
            smallNbrs = srcNbrs;
            largeNbrs = tarNbrs;
        } else {
            smallNbrs = tarNbrs;
            largeNbrs = srcNbrs;
        }
        Iterator<Object> it = smallNbrs.iterator();
        Integer counter = 0;
        while (it.hasNext()) {
            Object nbr = it.next();
            if (!nbr.equals(src) && !nbr.equals(tar) && largeNbrs.contains(nbr)) {
                counter++;
            }
        }

        concurrentTrianglesMap.get(src).addAndGet(counter);
        concurrentTrianglesMap.get(tar).addAndGet(counter);
    }

    public Map<Object, Integer> getTrianglesMap() {
        return trianglesMap;
    }

    public Integer getTotalTriangles() {
        return totalTriangles;
    }

    public Map<Object, Double> getClusteringCoefficientMap() {
        return clusteringCoefficientMap;
    }

    public Double getAvgClusteringCoeff() {
        return avgClusteringCoeff;
    }

    public static void main(String[] args) throws Exception {
        Map<Object, List<Object>> outNeighborMap = new HashMap<>();
//        List<String> n1 = Arrays.asList("2", "3");
//        List<String> n2 = Arrays.asList("4");
//        List<String> n3 = Arrays.asList("4", "5");
//        List<String> n4 = Arrays.asList("1", "6");
//        List<String> n5 = Arrays.asList("6");
//        List<String> n6 = new ArrayList<>();
//        List<String> n1 = Arrays.asList("2", "3", "4");
//        List<String> n2 = Arrays.asList("1", "4");
//        List<String> n3 = Arrays.asList("1", "4", "5");
//        List<String> n4 = Arrays.asList("1", "6", "2", "3");
//        List<String> n5 = Arrays.asList("3","6");
//        List<String> n6 = Arrays.asList("4","5");
//        List<String> n7 = Arrays.asList("8");
//        List<String> n8 = Arrays.asList("7");
//        List<String> n9 = new ArrayList<>();
//        outNeighborMap.put("1", n1);
//        outNeighborMap.put("2", n2);
//        outNeighborMap.put("3", n3);
//        outNeighborMap.put("4", n4);
//        outNeighborMap.put("5", n5);
//        outNeighborMap.put("6", n6);
//        outNeighborMap.put("7", n7);
//        outNeighborMap.put("8", n8);
//        outNeighborMap.put("9", n9);

        ClusteringCoefficient cc = new ClusteringCoefficient(outNeighborMap);
        cc.execute();
        Double v1 = cc.getAvgClusteringCoeff();
        Integer v2 = cc.getTotalTriangles();
        Map v3 = cc.getClusteringCoefficientMap();
        Map v4 = cc.getTrianglesMap();
    }


}
